﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Input;

namespace Gmantis.Controls
{
    public class CommandExtensions
    {
        // Fields
        public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.RegisterAttached("CommandParameter", typeof(object), typeof(CommandExtensions), new PropertyMetadata(null, new PropertyChangedCallback(CommandExtensions.OnCommandParameterChanged)));
        public static readonly DependencyProperty CommandProperty = DependencyProperty.RegisterAttached("Command", typeof(object), typeof(CommandExtensions), new PropertyMetadata(null, new PropertyChangedCallback(CommandExtensions.OnCommandChanged)));
        public static readonly DependencyProperty CommandTargetProperty = DependencyProperty.RegisterAttached("CommandTarget", typeof(UIElement), typeof(CommandExtensions), new PropertyMetadata(null, new PropertyChangedCallback(CommandExtensions.OnCommandTargetChanged)));
        private static readonly CommandToElementsMap s_CommandToElementsMap = new CommandToElementsMap();

        // Methods
        public static object GetCommand(FrameworkElement element)
        {
            return element.GetValue(CommandProperty);
        }

        public static object GetCommandParameter(FrameworkElement element)
        {
            return element.GetValue(CommandParameterProperty);
        }

        public static UIElement GetCommandTarget(FrameworkElement element)
        {
            return (UIElement)element.GetValue(CommandTargetProperty);
        }

        private static void OnButtonBaseClick(object sender, System.Windows.RoutedEventArgs e)
        {
            ButtonBase base2 = sender as ButtonBase;
            ICommand command = GetCommand(base2) as ICommand;
            object commandParameter = GetCommandParameter(base2);
            Command command2 = command as Command;
            if (command2 == null)
            {
                if ((command != null) && command.CanExecute(commandParameter))
                {
                    command.Execute(commandParameter);
                }
            }
            else
            {
                UIElement target = GetCommandTarget(base2) ?? base2;
                if (command2.CanExecute(commandParameter, target))
                {
                    command2.ExecuteImpl(commandParameter, target, sender);
                }
            }
        }

        private static void OnCommandChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            FrameworkElement element = obj as FrameworkElement;
            if (element == null)
            {
                throw new ArgumentException("You must set the Command attached property on an element that derives from FrameworkElement.");
            }
            ICommand oldValue = e.OldValue as ICommand;
            if (oldValue != null)
            {
                s_CommandToElementsMap.RemoveElementFromMap(element, oldValue);
                if (element is ButtonBase)
                {
                    ((ButtonBase)element).Click -= new RoutedEventHandler(CommandExtensions.OnButtonBaseClick);
                }
            }
            ICommand newValue = e.NewValue as ICommand;
            if (newValue != null)
            {
                s_CommandToElementsMap.AddElementToMap(element, newValue);
                ButtonBase button = element as ButtonBase;
                if (button != null)
                {
                    button.Click += new RoutedEventHandler(CommandExtensions.OnButtonBaseClick);
                    UpdateButtonCanExecute(button, newValue);
                }
            }
        }

        private static void OnCommandParameterChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            FrameworkElement element = obj as FrameworkElement;
            if (element == null)
            {
                throw new ArgumentException("You must set the CommandParameter attached property on an element that derives from FrameworkElement.");
            }
            ICommand command = GetCommand(element) as ICommand;
            if (command != null)
            {
                ButtonBase button = element as ButtonBase;
                if (button != null)
                {
                    UpdateButtonCanExecute(button, command, e.NewValue, GetCommandTarget(button));
                }
            }
        }

        private static void OnCommandTargetChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            FrameworkElement element = obj as FrameworkElement;
            if (element == null)
            {
                throw new ArgumentException("You must set the CommandTarget attached property on an element that derives from FrameworkElement.");
            }
            ICommand command = GetCommand(element) as ICommand;
            if (command != null)
            {
                ButtonBase button = element as ButtonBase;
                if (button != null)
                {
                    UpdateButtonCanExecute(button, command, GetCommandParameter(button), (UIElement)e.NewValue);
                }
            }
        }

        internal static void ResolveDeferredCommands()
        {
            s_CommandToElementsMap.ResolveDeferredCommands();
        }

        public static void SetCommand(FrameworkElement element, object value)
        {
            ICommand command = TryResolveCommand(value);
            if ((command != null) || (value == null))
            {
                element.SetValue(CommandProperty, command);
            }
            else
            {
                s_CommandToElementsMap.AddDeferredCommand(element, (string)value);
            }
        }

        public static void SetCommandParameter(FrameworkElement element, object value)
        {
            element.SetValue(CommandParameterProperty, value);
        }

        public static void SetCommandTarget(FrameworkElement element, UIElement value)
        {
            element.SetValue(CommandTargetProperty, value);
        }

        internal static ICommand TryResolveCommand(object cmd)
        {
            if (cmd is ICommand)
            {
                return (cmd as ICommand);
            }
            CommandConverter converter = new CommandConverter();
            return (converter.ConvertFrom(null, CultureInfo.InvariantCulture, cmd) as ICommand);
        }

        private static void UpdateButtonCanExecute(ButtonBase button, ICommand command)
        {
            UpdateButtonCanExecute(button, command, GetCommandParameter(button), GetCommandTarget(button));
        }

        private static void UpdateButtonCanExecute(ButtonBase button, ICommand command, object parameter, UIElement commandTarget)
        {
            Command command2 = command as Command;
            if (command2 == null)
            {
                button.IsEnabled = command.CanExecute(parameter);
            }
            else
            {
                button.IsEnabled = command2.CanExecute(parameter, commandTarget ?? button);
            }
        }

        internal static void UpdateCanExecute()
        {
            using (List<ICommand>.Enumerator enumerator = s_CommandToElementsMap.Commands.GetEnumerator())
            {
                while (enumerator.MoveNext())
                {
                    Action<FrameworkElement> callback = null;
                    ICommand command = enumerator.Current;
                    if (callback == null)
                    {
                        callback = delegate(FrameworkElement element)
                        {
                            ButtonBase button = element as ButtonBase;
                            if (button != null)
                            {
                                UpdateButtonCanExecute(button, command);
                            }
                        };
                    }
                    s_CommandToElementsMap.ForEachElement(command, callback);
                }
            }
        }
    }
}
